using System;
using System.Collections.Generic;
using System.Linq;
using DataAccess.BusinessEntities;
using DataAccess.Services;
using System.Collections;

namespace DataAccess.UnitTests
{
    public class InMemoryDomainContext : IDomainContext, IFastDomainContext
    {
        public bool Saved { get; private set; }
        public bool Disposed { get; private set; }

        private readonly HashSet<Product> productSet;
        private readonly HashSet<PersonalLocation> personalLocationSet;
        private readonly HashSet<Store> storeSet;
        private readonly HashSet<User> userSet;
        private readonly HashSet<Substance> substanceSet;
        private readonly HashSet<Container> containerSet;

        private readonly Dictionary<int, Product> productDict;
        private readonly Dictionary<int, PersonalLocation> personalLocationDict;
        private readonly Dictionary<int, Store> storeDict;
        private readonly Dictionary<int, User> userDict;
        private readonly Dictionary<int, Substance> substanceDict;
        private readonly Dictionary<int, Container> containerDict;

        Dictionary<Type, IDictionary> sets;

        /// <summary>
        /// Initializes a new instance of the InMemoryDomainContext class.
        /// </summary>
        public InMemoryDomainContext()
        {
            this.productSet = new HashSet<Product>();
            this.personalLocationSet = new HashSet<PersonalLocation>();
            this.storeSet = new HashSet<Store>();
            this.userSet = new HashSet<User>();
            this.substanceSet = new HashSet<Substance>();
            this.containerSet = new HashSet<Container>();

            this.productDict = new Dictionary<int, Product>();
            this.personalLocationDict = new Dictionary<int, PersonalLocation>();
            this.storeDict = new Dictionary<int, Store>();
            this.userDict = new Dictionary<int, User>();
            this.substanceDict = new Dictionary<int, Substance>();
            this.containerDict = new Dictionary<int, Container>();

            this.sets = new Dictionary<Type, IDictionary>();
            this.sets.Add(typeof(Product), productDict);
            this.sets.Add(typeof(PersonalLocation), personalLocationDict);
            this.sets.Add(typeof(Store), storeDict);
            this.sets.Add(typeof(User), userDict);
            this.sets.Add(typeof(Substance), substanceDict);
            this.sets.Add(typeof(Container), containerDict);
        }

        public void SaveChanges()
        {
            this.Saved = true;
        }

        public void Save<T>(T entity) where T : class, IEntity
        {
            if (!this.LocateSetFor<T>().Add(entity))
            {
                throw new ArgumentException("Entity already added to hashset");
            };

            this.LocateDictionaryFor<T>().Add(entity.Id, entity);
        }

        public void Populate<T>(IEnumerable<T> entities) where T : class, IEntity
        {
            HashSet<T> set = this.LocateSetFor<T>();
            set.Clear();
            foreach (T entity in entities)
            {
                set.Add(entity);
            }
        }

        public void PopulateDictionary<T>(IEnumerable<T> entities) where T : class, IEntity
        {
            Dictionary<int, T> dictionary = this.LocateDictionaryFor<T>();
            dictionary.Clear();
            foreach (T entity in entities)
            {
                dictionary.Add(entity.Id, entity);
            }
        }

        private Dictionary<int, T> LocateDictionaryFor<T>() where T : class, IEntity
        {
            IDictionary dict;
            this.sets.TryGetValue(typeof(T), out dict);
            Dictionary<int, T> result = dict as Dictionary<int, T>;
            return result;
        }

        private HashSet<T> LocateSetFor<T>() where T : class, IEntity
        {
            if (typeof(T) == typeof(Product))
            {
                return this.productSet as HashSet<T>;
            }
            else if (typeof(T) == typeof(PersonalLocation))
            {
                return this.personalLocationSet as HashSet<T>;
            }
            else if (typeof(T) == typeof(Store))
            {
                return this.storeSet as HashSet<T>;
            }
            else if (typeof(T) == typeof(User))
            {
                return this.userSet as HashSet<T>;
            }
            else if (typeof(T) == typeof(Substance))
            {
                return this.substanceSet as HashSet<T>;
            }
            else if (typeof(T) == typeof(Container))
            {
                return this.containerSet as HashSet<T>;
            }
            else
            {
                throw new ArgumentException(String.Format("No set defined for entity of type{0}", typeof(T)));
            }
        }

        public void Delete<T>(int id) where T : class, IEntity
        {
            this.LocateSetFor<T>().FirstOrDefault(x => x.Id == id).IsDeleted = true;
            this.LocateDictionaryFor<T>()[id].IsDeleted = true;
        }

        public IQueryable<Product> Products
        {
            get { return this.productSet.AsQueryable(); }
        }

        public IQueryable<PersonalLocation> PersonalLocations
        {
            get { return this.personalLocationSet.AsQueryable(); }
        }

        public IQueryable<Store> Stores
        {
            //get { return this.storeSet.AsQueryable().Where(s => !s.IsDeleted); }
            get { return this.storeDict.Values.AsQueryable()/*.Where(s => !s.IsDeleted)*/; }
        }

        public IQueryable<User> Users
        {
            get { return this.userSet.AsQueryable(); }
        }

        public IQueryable<Substance> Substances
        {
            get { return this.substanceSet.AsQueryable(); }
        }

        public IQueryable<Container> Containers
        {
            get { return this.containerSet.AsQueryable(); }
        }

        public void Dispose()
        {
            this.Disposed = true;
        }
    }
}
